// Copyright 2022 jeff.li. and/or its affiliates.


/*!
 * \file tbir/printer/doc.h
 * \brief Doc ADT used for pretty printing.
 *
 *  Reference: Philip Wadler. A Prettier Printer. Journal of Functional Programming'98
 */
#pragma once

#include <string>
#include <type_traits>
#include <vector>

#include <tbir/runtime/container.h>
#include <tbir/runtime/data_type.h>
#include <tbir/runtime/object.h>

namespace tbir::printer {

    using runtime::Object;
    using runtime::ObjectPtr;
    using runtime::ObjectRef;

    /*!
     * \brief Doc atom node for the ADT.
     * \sa DocAtom
     */
    class DocAtomNode : public Object {
    public:
        static constexpr const char *_type_key = "printer.DocAtom";
        TBIR_DECLARE_BASE_OBJECT_INFO(DocAtomNode, Object);
    };

    /*!
     * \brief Managed reference to DocAtomNode.
     * \sa DocAtomNode.
     */
    class DocAtom : public ObjectRef {
    public:
        TBIR_DEFINE_OBJECT_REF_METHODS(DocAtom, ObjectRef, DocAtomNode);
    };

    /*!
     * \brief Stream-like interface for Doc DSL.
     *
     * The Doc DSL de-couples the layout decision from the printing decision.
     *
     * The layout(code formating) decisions include:
     * - Change indentation.
     * - Break single line into multiple ones(subjected to future improvements).
     */
    class Doc {
    public:
        /*! \brief default constructor */
        Doc() {
        }

        /*!
         * \brief Append right to the end of the current doc stream.
         * \param right The doc to be appended.
         * \return reference to self.
         */
        Doc &operator<<(const Doc &right);

        /*!
         * \brief Append right to the end of the current doc stream.
         * \param right The doc to be appended.
         * \return reference to self.
         * \note pass by value to allow copy elison optimization.
         */
        Doc &operator<<(runtime::String right);

        /*!
         * \brief Append right to the end of the current doc stream.
         * \param right The doc to be appended.
         * \return reference to self.
         */
        Doc &operator<<(const DocAtom &right);

        /*!
         * \brief Convert value to string via std::ostreamstream
         *        the append to the current doc stream.
         * \param right The doc to be appended.
         * \tparam T the type of the value.
         * \return reference to self.
         */
        template<typename T, typename = typename std::enable_if<!std::is_class<T>::value>::type>
        Doc &operator<<(const T &value) {
            std::ostringstream os;
            os << value;
            return *this << os.str();
        }

        /*!
         * \brief Convert the doc stream into string.
         * \return The string representation.
         */
        runtime::String str();

        /*!
         * \brief Create a doc that represents text content.
         * \return The created doc.
         */
        static Doc Text(runtime::String value);

        /*!
         * \brief Create a doc that represents raw text(can have new lines)
         * \return The created doc.
         */
        static Doc RawText(runtime::String value);

        /*!
         * \brief Create a doc that represents a new line.
         * \return The created doc.
         */
        static Doc NewLine(int indent = 0);

        /*!
         * \brief Create a new doc that adds indentation to everyline of the doc.
         * \param indent The indent to be added.
         * \param doc The doc to be indented.
         * \return The created doc.
         * \note pass by value to allow copy elison optimization.
         */
        static Doc Indent(int indent, Doc doc);

        /*!
         * \brief Create a Doc that represents a string literal.
         * \param value The content of the string literal.
         * \param quote The quote in the literal.
         * \return The created doc.
         */
        static Doc StrLiteral(const runtime::String &value, const runtime::String &quote = "\"");

        /*!
         * \brief Create a Doc that represents a boolean literal in python syntax.
         * \param value The bool value.
         * \return The created doc.
         */
        static Doc PyBoolLiteral(bool value);

        /*!
         * \brief Enclose body by brace and add indent.
         * \param body The body
         * \param open The open brace.
         * \param close The close brace.
         * \param indent amount of indentation.
         * \return The created doc.
         */
        static Doc Brace(runtime::String open, const Doc &body, runtime::String close, int indent = 2);

        /*!
         * \brief Create a doc by concatenating  together with separator.
         * \param vec The docs to be concatenated.
         * \param sep The seperator.
         * \return The created doc.
         */
        static Doc Concat(const std::vector<Doc> &vec, const Doc &sep = Text(", "));

    private:
        /*! \brief Internal doc stream. */
        std::vector<DocAtom> stream_;
    };

}  // namespace tbir::printer
